/*
 * Copyright (c) 2004-2022, University of Oslo
 * All rights reserved.
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions are met:
 * Redistributions of source code must retain the above copyright notice, this
 * list of conditions and the following disclaimer.
 *
 * Redistributions in binary form must reproduce the above copyright notice,
 * this list of conditions and the following disclaimer in the documentation
 * and/or other materials provided with the distribution.
 * Neither the name of the HISP project nor the names of its contributors may
 * be used to endorse or promote products derived from this software without
 * specific prior written permission.
 *
 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
 * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
 * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
 * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR
 * ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
 * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
 * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON
 * ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
 * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 */
package org.hisp.dhis.common;

import java.io.Serializable;
import java.sql.ResultSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
import net.sf.jasperreports.engine.JRDataSource;
import org.springframework.jdbc.support.rowset.SqlRowSet;
import org.springframework.jdbc.support.rowset.SqlRowSetMetaData;

/**
 * Represents a two-dimensional grid of Object-typed values organized in rows and columns.
 *
 * @author Lars Helge Overland
 */
public interface Grid extends JRDataSource, Serializable {
  /** Returns the grid title. */
  String getTitle();

  /** Sets the grid title. */
  Grid setTitle(String title);

  /** Returns the grid subtitle. */
  String getSubtitle();

  /** Sets the grid subtitle. */
  Grid setSubtitle(String subtitle);

  /** Returns the grid table. */
  String getTable();

  /** Sets the grid table. */
  Grid setTable(String table);

  /** Returns all header values. */
  List<GridHeader> getHeaders();

  /** Returns map of meta-data. */
  Map<String, Object> getMetaData();

  /** Sets map of meta-data. */
  Grid setMetaData(Map<String, Object> metaData);

  /** Adds a key-value pair to meta-data. */
  Grid addMetaData(String key, Object value);

  /** Returns a map of internal meta-data. */
  Map<String, Object> getInternalMetaData();

  /** Returns a map of row contextual data. */
  Map<Integer, Map<String, Object>> getRowContext();

  /** Returns performance metrics. */
  PerformanceMetrics getPerformanceMetrics();

  /** Sets a map of internal meta-data. */
  Grid setInternalMetaData(Map<String, Object> internalMetaData);

  /** Returns all visible headers, ie. headers which are not hidden. */
  List<GridHeader> getVisibleHeaders();

  /** Returns all metadata headers. */
  List<GridHeader> getMetadataHeaders();

  /**
   * Returns the index of the header with the given name, or -1 if no matching header exists.
   *
   * @param name the name of the grid header.
   */
  int getIndexOfHeader(String name);

  /**
   * Indicates whether a header with the given name exists.
   *
   * @param name the name of the grid header.
   */
  default boolean headerExists(String name) {
    return getIndexOfHeader(name) != -1;
  }

  /**
   * Adds a header.
   *
   * @param header the grid header.
   */
  Grid addHeader(GridHeader header);

  /**
   * Adds a header value at the given column index.
   *
   * @param headerIndex the index to insert the grid header at.
   * @param header the grid header.
   */
  Grid addHeader(int headerIndex, GridHeader header);

  /**
   * Adds a list of headers.
   *
   * @param headerIndex the index to insert the first grid header at.
   * @param headers list of headers.
   */
  Grid addHeaders(int headerIndex, List<GridHeader> headers);

  /**
   * Adds a number of empty values to the Grid.
   *
   * @param number the number of columns to add.
   */
  Grid addEmptyHeaders(int number);

  /**
   * Replaces the list of headers.
   *
   * @param headers list of headers.
   */
  Grid replaceHeaders(List<GridHeader> headers);

  /** Returns the current height / number of rows in the grid. */
  int getHeight();

  /** Returns the current width based on the number of columns in the grid. */
  int getWidth();

  /** Returns the current width based on the number of headers in the grid. */
  int getHeaderWidth();

  /** Returns the current width / number of visible columns in the grid. */
  int getVisibleWidth();

  /** Adds a new row the the grid and moves the cursor accordingly. */
  Grid addRow();

  /**
   * Adds all rows of the given grid to this grid.
   *
   * @param grid the grid to add to this grid.
   */
  Grid addRows(Grid grid);

  /**
   * Adds the value to the end of the current row.
   *
   * @param value the value to add.
   */
  Grid addValue(Object value);

  /**
   * Adds values in the given array to the end of the current row in the specified order.
   *
   * @param values the values to add.
   */
  Grid addValues(Object[] values);

  /**
   * Adds the given values to the end of the current row in the specified order.
   *
   * @param values the values to add.
   */
  Grid addValuesVar(Object... values);

  /**
   * Adds values in the given array to the end of the current row in the specified order.
   *
   * @param values the values to add.
   */
  Grid addValuesAsList(List<Object> values);

  /** Adds an empty value to the Grid at the current row. */
  Grid addEmptyValue();

  /**
   * Adds a number of empty values to the Grid at the current row.
   *
   * @param number the number of empty values to add.
   */
  Grid addEmptyValues(int number);

  /**
   * Adds a number of null values to the Grid at the current row.
   *
   * @param number the number of null values to add.
   */
  Grid addNullValues(int number);

  /**
   * Returns the row with the given index.
   *
   * @param rowIndex the index of the row.
   */
  List<Object> getRow(int rowIndex);

  /** Returns all rows. */
  List<List<Object>> getRows();

  /** Returns references. */
  List<Reference> getRefs();

  /** Returns all visible rows, ie. rows with a corresponding header that is not hidden. */
  List<List<Object>> getVisibleRows();

  /**
   * Returns the column with the given index.
   *
   * @param columnIndex the index of the column.
   */
  List<Object> getColumn(int columnIndex);

  /**
   * Return the value at the given row index and the given column index.
   *
   * @param rowIndex the row index.
   * @param columnIndex the column index.
   * @return the column value.
   * @throws IllegalArgumentException if the grid does not contain the requested row / column.
   */
  Object getValue(int rowIndex, int columnIndex);

  /**
   * Adds a new column at the end of the grid.
   *
   * @param columnValues the column values to add.
   * @throws IllegalStateException if the columnValues has different length than the rows in grid,
   *     or if the grid rows are not of the same length.
   */
  Grid addColumn(List<Object> columnValues);

  /**
   * Adds a new column at the end of the grid.
   *
   * @param columnIndex the index at where to insert the column.
   * @param columnValues the column values to add.
   * @throws IllegalStateException if the columnValues has different length than the rows in grid,
   *     or if the grid rows are not of the same length.
   */
  Grid addColumn(int columnIndex, List<Object> columnValues);

  /**
   * Adds and populates the given number of columns before the given reference column index. The
   * given value map is to populate each row, where they key is matched against the values in the
   * reference column, and the list of values is used to populate the row values left to right.
   * Where there is no match, null values are inserted.
   *
   * @param referenceColumnIndex the reference column index.
   * @param valueMap the map of values to list of values.
   * @param newColumns the number of new columns to add.
   */
  Grid addAndPopulateColumnsBefore(
      int referenceColumnIndex, Map<Object, List<?>> valueMap, int newColumns);

  /**
   * Removes the header and column at the given index.
   *
   * @param columnIndex the column index.
   */
  Grid removeColumn(int columnIndex);

  /** Removes from the grid columns with corresponding headers which only contain null values. */
  Grid removeEmptyColumns();

  /**
   * Indicates whether the column with the given index only contains null values.
   *
   * @param columnIndex the column index.
   */
  boolean columnIsEmpty(int columnIndex);

  /**
   * Removes the header and the column at the index of the given header if it exists.
   *
   * @param header the grid header.
   */
  Grid removeColumn(GridHeader header);

  /** Removes the current row from the grid. */
  Grid removeCurrentWriteRow();

  /**
   * Indicates whether meta data exists and contains the given key.
   *
   * @param key the meta data key.
   */
  boolean hasMetaDataKey(String key);

  /**
   * Indicates whether the internal meta data exists and contains the given key.
   *
   * @param key the internal meta data key.
   */
  boolean hasInternalMetaDataKey(String key);

  /**
   * Limits the grid from top by the given argument number.
   *
   * @param limit the top limit, must be greater than zero to have an effect.
   */
  Grid limitGrid(int limit);

  /**
   * Limits the grid by the given start and end position.
   *
   * @param startPos the start position.
   * @param endPos the end position.
   */
  Grid limitGrid(int startPos, int endPos);

  /**
   * Sorts the grid ascending on the column at the given columnIndex.
   *
   * @param columnIndex the column index, starting on 1.
   * @param order a negative value indicates ascending order, a positive value indicates descending
   *     order, zero value indicates no sorting.
   */
  Grid sortGrid(int columnIndex, int order);

  /**
   * Adds a regression column to the grid. Column must hold numeric data.
   *
   * @param columnIndex the index of the base column.
   * @param addHeader indicates whether to add a grid header for the regression column.
   */
  Grid addRegressionColumn(int columnIndex, boolean addHeader);

  /**
   * Adds columns with regression values to the given grid.
   *
   * @param startColumnIndex the index of the first data column.
   * @param numberOfColumns the number of data columns.
   */
  Grid addRegressionToGrid(int startColumnIndex, int numberOfColumns);

  /**
   * Adds a cumulative column to the grid. Column must hold numeric data.
   *
   * @param columnIndex the index of the base column.
   * @param addHeader indicates whether to add a grid header for the regression column.
   */
  Grid addCumulativeColumn(int columnIndex, boolean addHeader);

  /**
   * Adds columns with cumulative values to the given grid.
   *
   * @param startColumnIndex the index of the first data column.
   * @param numberOfColumns the number of data columns.
   */
  Grid addCumulativesToGrid(int startColumnIndex, int numberOfColumns);

  /**
   * Substitutes the grid header names and the grid values for meta type columns based on the given
   * mapping. Values are left unchanged when there is not match in the given meta data map.
   *
   * @param metaDataMap meta data map of keys and value substitutions.
   */
  Grid substituteMetaData(Map<?, ?> metaDataMap);

  /**
   * Substitutes the grid values based on the given mapping. Substitutes values per row based on the
   * given source column index and target column index. Values are left unchanged when there is not
   * match in the given meta data map.
   *
   * @param sourceColumnIndex the index of the column to read values.
   * @param targetColumnIndex the index of the column to substitute values.
   * @param metaDataMap meta-data map of keys and substitutions.
   */
  Grid substituteMetaData(int sourceColumnIndex, int targetColumnIndex, Map<?, ?> metaDataMap);

  /** Returns indexes of the meta grid headers. */
  List<Integer> getMetaColumnIndexes();

  /**
   * Returns the unique set of values from the grid column with the given name. The name refers to
   * the name of the grid header of the column.
   *
   * @param columnName name of the column grid header.
   */
  Set<Object> getUniqueValues(String columnName);

  /**
   * Returns a map of each row in the grid.
   *
   * @param valueIndex the index of the column to use as map values.
   * @param keySeparator the separator to use to concatenate the map key.
   */
  <T> Map<String, T> getAsMap(int valueIndex, String keySeparator);

  /**
   * Adds a set of headers based on the column names of the given SQL result set.
   *
   * @param rs the result set.
   */
  Grid addHeaders(ResultSet rs);

  /**
   * Adds a set of headers based on the column names of the given SQL row set.
   *
   * @param rs the result set.
   */
  Grid addHeaders(SqlRowSet rs);

  /**
   * Adds a set of headers based on the column names of the given SQL row set. If 'withTypes' are
   * set to true, the method will dynamically set the header type based on the row set metadata.
   *
   * @param rowSetMetaData the result set.
   * @param withTypes if true, it will set the respective header type.
   */
  Grid addHeaders(SqlRowSetMetaData rowSetMetaData, boolean withTypes);

  /**
   * Moves the cursor the next row and adds values for each column of the given SQL result set.
   *
   * @param rs the result set.
   */
  Grid addRows(ResultSet rs);

  /**
   * Moves the cursor the next row and adds values for each column of the given SQL row set.
   *
   * @param rs the row set.
   */
  Grid addRows(SqlRowSet rs);

  /**
   * Moves the cursor the next row and adds values for each column of the given SQL row set.
   *
   * @param rs the row set.
   * @param maxLimit the max number of records to return, use {@code -1} for no limit.
   */
  Grid addRows(SqlRowSet rs, int maxLimit);

  /**
   * Adds performance metrics.
   *
   * @param plans the list of execution plans.
   */
  Grid addPerformanceMetrics(List<ExecutionPlan> plans);

  /**
   * Set Row Context, the contextual information describing origin of values (columns) inside the
   * row
   *
   * @param rowContext
   * @return Grid instance
   */
  Grid setRowContext(Map<Integer, Map<String, Object>> rowContext);

  /**
   * Adds a reference.
   *
   * @param reference the reference.
   */
  Grid addReference(Reference reference);

  /**
   * This method will retain the given columns in the current Grid. If the set of columns provided
   * is null or empty, no changes will be made to the Grid. It will also keep the order of the
   * columns based on the given set of headers.
   *
   * @param columns set of columns to be retained, on the same order.
   */
  void retainColumns(Set<String> columns);

  /**
   * Reorders the headers of the grid based on the given list of header names.
   *
   * @param headers the list of header names.
   * @return a set of indexes which holds the new header order.
   */
  List<Integer> repositionHeaders(List<String> headers);

  /**
   * Reorders the headers and columns of the grid based on the given column indexes. The given
   * column indexes refer to the current column indexes.
   *
   * @param columnIndexes the new column indexes.
   */
  void repositionColumns(List<Integer> columnIndexes);

  boolean hasLastDataRow();

  void setLastDataRow(boolean lastDataRow);
}
